/*============================================================================
 PROJECT: Talklife
 FILE:    RichTextView.m
 AUTHOR:  Khoai Nguyen
 DATE:    7/5/13
 =============================================================================*/

/*============================================================================
 IMPORT
 =============================================================================*/
#import "RichTextView.h"
#import "MarkupParser.h"

/*============================================================================
 PRIVATE MACRO
 =============================================================================*/
/*============================================================================
 PRIVATE INTERFACE
 =============================================================================*/


@implementation RichTextView

- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
        self.defaultColor = [UIColor whiteColor];
        if(SupportiOS7) {
            self.lineBreakMode = NSLineBreakByTruncatingTail;
        } else {
            self.lineBreakMode = UILineBreakModeTailTruncation;
        }
        self.font = FontArialWithSize(13);
        self.contentText = @"";
    }
    return self;
}

- (void)awakeFromNib {
    [super awakeFromNib];
    if(SupportiOS7) {
        self.lineBreakMode = NSLineBreakByTruncatingTail;
    } else {
        self.lineBreakMode = UILineBreakModeTailTruncation;
    }
    self.defaultColor = [UIColor whiteColor];
    self.font = FontArialWithSize(13);
    self.contentText = @"";
}

- (void)applyTextAlignment:(NSMutableAttributedString *)attributedString {
    /* apply text alignment */
    CTTextAlignment alignment = kCTLeftTextAlignment;
    switch (self.textAlignment) {
        case NSTextAlignmentLeft:
            alignment = kCTLeftTextAlignment;
            break;
            
        case NSTextAlignmentRight:
            alignment = kCTRightTextAlignment;
            break;
            
        case NSTextAlignmentCenter:
            alignment = kCTCenterTextAlignment;
            break;
            
        case NSTextAlignmentJustified:
            alignment = kCTJustifiedTextAlignment;
            break;
            
        case NSTextAlignmentNatural:
            alignment = kCTNaturalTextAlignment;
            break;
    }
    
    CGFloat topSpacing = self.topSpacing;
    CGFloat lineHeight = self.lineHeight;
    
    CTParagraphStyleSetting settings[] =
    {
        {kCTParagraphStyleSpecifierAlignment, sizeof(alignment), &alignment},
		{kCTParagraphStyleSpecifierMaximumLineHeight, sizeof(CGFloat), &lineHeight},
		{kCTParagraphStyleSpecifierMinimumLineHeight, sizeof(CGFloat), &lineHeight},
		{kCTParagraphStyleSpecifierLineSpacingAdjustment, sizeof(CGFloat), &_spaceBetweenLines},
        {kCTParagraphStyleSpecifierParagraphSpacingBefore, sizeof(int), &topSpacing}
    };
    
    CTParagraphStyleRef paragraphRef = CTParagraphStyleCreate(settings, sizeof(settings)/sizeof(CTParagraphStyleSetting));
	CFAttributedStringSetAttribute((CFMutableAttributedStringRef)attributedString, CFRangeMake(0, CFAttributedStringGetLength((CFMutableAttributedStringRef)attributedString)), kCTParagraphStyleAttributeName, paragraphRef);
	
	CFNumberRef kernValue = (__bridge CFNumberRef) [NSNumber numberWithFloat:_textKerning];
	CFAttributedStringSetAttribute((CFMutableAttributedStringRef)attributedString, CFRangeMake(0, CFAttributedStringGetLength((CFMutableAttributedStringRef)attributedString)), kCTKernAttributeName, kernValue);
    
    CFRelease(paragraphRef);
}

#pragma mark - Overriden Methods

- (NSAttributedString *)attributedStringAfterUpdateFormatStyles {
    NSMutableAttributedString *attributedString = nil;
    @autoreleasepool {
        /* apply html format & defaul font */
        MarkupParser *parser = [[MarkupParser alloc] init];
        parser.font = self.font;
        parser.color = self.defaultColor;
        attributedString = [parser attrStringFromMarkup:self.contentText];
        
        /* apply alignment */
        [self applyTextAlignment:attributedString];
    }
    return attributedString;
}

- (CGRect)textRectForBounds:(CGRect)bounds
     limitedToNumberOfLines:(NSInteger)numberOfLines {
    
    CGRect textRect = bounds;
    
    // Calculate height with a minimum of double the font pointSize, to ensure that CTFramesetterSuggestFrameSizeWithConstraints doesn't return CGSizeZero, as it would if textRect height is insufficient.
    textRect.size.height = fmaxf(self.font.pointSize * 2.0f, bounds.size.height);
    
    // Adjust the text to be in the center vertically, if the text size is smaller than bounds
    CGSize textSize = CTFramesetterSuggestFrameSizeWithConstraints(self.framesetter, CFRangeMake(0, [self.attributedString length]), NULL, textRect.size, NULL);
    textSize = CGSizeMake(ceilf(textSize.width), ceilf(textSize.height)); // Fix for iOS 4, CTFramesetterSuggestFrameSizeWithConstraints sometimes returns fractional sizes
    
    if (textSize.height < textRect.size.height) {
        CGFloat yOffset = 0.0f;
        switch (self.contentAlignment) {
            case RTContentAlignmentVerticalCenter:
                yOffset = floorf((bounds.size.height - textSize.height) / 2.0f);
                break;
            case RTContentAlignmentVerticalBottom:
                yOffset = bounds.size.height - textSize.height;
                break;
            case RTContentAlignmentVerticalTop:
            default:
                break;
        }
        
        textRect.origin.y += yOffset;
    }
    
    return textRect;
}

- (void)drawFramesetter:(CTFramesetterRef)framesetter
       attributedString:(NSAttributedString *)attributedString
              textRange:(CFRange)textRange
                 inRect:(CGRect)rect
                context:(CGContextRef)c {
    
    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddRect(path, NULL, rect);
    CTFrameRef frame = CTFramesetterCreateFrame(framesetter, textRange, path, NULL);
    
    CFArrayRef lines = CTFrameGetLines(frame);
    NSInteger numberOfLines = self.numberOfLines > 0 ? MIN(self.numberOfLines, CFArrayGetCount(lines)) : CFArrayGetCount(lines);
    self.numberOfLines = numberOfLines;
    
    BOOL truncateLastLine;
    if(SupportiOS7) {
        truncateLastLine = (self.lineBreakMode == NSLineBreakByTruncatingHead || self.lineBreakMode == NSLineBreakByTruncatingMiddle || self.lineBreakMode == NSLineBreakByTruncatingTail);
    } else {
        truncateLastLine = (self.lineBreakMode == UILineBreakModeHeadTruncation || self.lineBreakMode == UILineBreakModeMiddleTruncation || self.lineBreakMode == UILineBreakModeTailTruncation);
    }
    
	
    CGPoint lineOrigins[numberOfLines];
    CTFrameGetLineOrigins(frame, CFRangeMake(0, numberOfLines), lineOrigins);
    
    for (CFIndex lineIndex = 0; lineIndex < numberOfLines; lineIndex++) {
        CGPoint lineOrigin = lineOrigins[lineIndex];
        CGContextSetTextPosition(c, lineOrigin.x, lineOrigin.y);
        CTLineRef line = CFArrayGetValueAtIndex(lines, lineIndex);
        
        if (lineIndex == numberOfLines - 1 && truncateLastLine) {
            // Check if the range of text in the last line reaches the end of the full attributed string
            CFRange lastLineRange = CTLineGetStringRange(line);
            
            if (!(lastLineRange.length == 0 && lastLineRange.location == 0) && lastLineRange.location + lastLineRange.length < textRange.location + textRange.length) {
                // Get correct truncationType and attribute position
                CTLineTruncationType truncationType;
                NSUInteger truncationAttributePosition = lastLineRange.location;
                
                if(SupportiOS7) {
                    NSLineBreakMode lineBreakMode = self.lineBreakMode;
                    // Multiple lines, only use UILineBreakModeTailTruncation
                    if (numberOfLines != 1) {
                        lineBreakMode = NSLineBreakByTruncatingTail;
                    }
                    
                    switch (lineBreakMode) {
                        case NSLineBreakByTruncatingHead:
                            truncationType = kCTLineTruncationStart;
                            break;
                        case NSLineBreakByTruncatingMiddle:
                            truncationType = kCTLineTruncationMiddle;
                            truncationAttributePosition += (lastLineRange.length / 2);
                            break;
                        case NSLineBreakByTruncatingTail:
                        default:
                            truncationType = kCTLineTruncationEnd;
                            truncationAttributePosition += (lastLineRange.length - 1);
                            break;
                    }
                    
                } else {
                    UILineBreakMode lineBreakMode = self.lineBreakMode;
                    // Multiple lines, only use UILineBreakModeTailTruncation
                    if (numberOfLines != 1) {
                        lineBreakMode = UILineBreakModeTailTruncation;
                    }
                    
                    switch (lineBreakMode) {
                        case UILineBreakModeHeadTruncation:
                            truncationType = kCTLineTruncationStart;
                            break;
                        case UILineBreakModeMiddleTruncation:
                            truncationType = kCTLineTruncationMiddle;
                            truncationAttributePosition += (lastLineRange.length / 2);
                            break;
                        case UILineBreakModeTailTruncation:
                        default:
                            truncationType = kCTLineTruncationEnd;
                            truncationAttributePosition += (lastLineRange.length - 1);
                            break;
                    }
                    
                }
                
                
                // Get the attributes and use them to create the truncation token string
                NSDictionary *tokenAttributes = [attributedString attributesAtIndex:truncationAttributePosition effectiveRange:NULL];
                NSString *truncationTokenString = self.truncationTokenString;
                if (!truncationTokenString) {
                    truncationTokenString = @"\u2026"; // Unicode Character 'HORIZONTAL ELLIPSIS' (U+2026)
                }
                
                NSAttributedString *attributedTokenString = [[NSAttributedString alloc] initWithString:truncationTokenString attributes:tokenAttributes];
                CTLineRef truncationToken = CTLineCreateWithAttributedString((__bridge  CFAttributedStringRef)attributedTokenString);
                
                // Append truncationToken to the string
                // because if string isn't too long, CT wont add the truncationToken on it's own
                // There is no change of a double truncationToken because CT only add the token if it removes characters (and the one we add will go first)
                NSMutableAttributedString *truncationString = [[attributedString attributedSubstringFromRange:NSMakeRange(lastLineRange.location, lastLineRange.length)] mutableCopy];
                if (lastLineRange.length > 0) {
                    // Remove any newline at the end (we don't want newline space between the text and the truncation token). There can only be one, because the second would be on the next line.
                    unichar lastCharacter = [[truncationString string] characterAtIndex:lastLineRange.length - 1];
                    if ([[NSCharacterSet newlineCharacterSet] characterIsMember:lastCharacter]) {
                        [truncationString deleteCharactersInRange:NSMakeRange(lastLineRange.length - 1, 1)];
                    }
                }
                [truncationString appendAttributedString:attributedTokenString];
                CTLineRef truncationLine = CTLineCreateWithAttributedString((__bridge  CFAttributedStringRef)truncationString);
                
                // Truncate the line in case it is too long.
                CTLineRef truncatedLine = CTLineCreateTruncatedLine(truncationLine, rect.size.width, truncationType, truncationToken);
                if (!truncatedLine) {
                    // If the line is not as wide as the truncationToken, truncatedLine is NULL
                    truncatedLine = CFRetain(truncationToken);
                }
                
                CGFloat flushFactor = 0.0f;
                if(SupportiOS7) {
                    // Adjust pen offset for flush depending on text alignment
                    
                    switch (self.textAlignment) {
                        case NSTextAlignmentCenter:
                            flushFactor = 0.5f;
                            break;
                        case NSTextAlignmentRight:
                            flushFactor = 1.0f;
                            break;
                        case NSTextAlignmentLeft:
                        default:
                            break;
                    }
                    
                } else {
                    // Adjust pen offset for flush depending on text alignment
                    switch (self.textAlignment) {
                        case UITextAlignmentCenter:
                            flushFactor = 0.5f;
                            break;
                        case UITextAlignmentRight:
                            flushFactor = 1.0f;
                            break;
                        case UITextAlignmentLeft:
                        default:
                            break;
                    }
                }
                CGFloat penOffset = CTLineGetPenOffsetForFlush(truncatedLine, flushFactor, rect.size.width);
                CGContextSetTextPosition(c, penOffset, lineOrigin.y);
                
                CTLineDraw(truncatedLine, c);
                CFRelease(truncatedLine);
                CFRelease(truncationLine);
                CFRelease(truncationToken);
            } else {
                CTLineDraw(line, c);
            }
        } else {
            CTLineDraw(line, c);
        }
    }
    
    CFRelease(frame);
    CFRelease(path);
}

- (void)drawRect:(CGRect)rect {
    /* adjust draw here */
    CGContextRef c = UIGraphicsGetCurrentContext();
    CGContextSaveGState(c); {
        CGContextSetTextMatrix(c, CGAffineTransformIdentity);
        CFRange textRange = CFRangeMake(0, [self.attributedString length]);
        
        // First, get the text rect (which takes vertical centering into account)
        CGRect textRect = [self textRectForBounds:rect limitedToNumberOfLines:self.numberOfLines];
        
        // CoreText draws it's text aligned to the bottom, so we move the CTM here to take our vertical offsets into account
        CGContextTranslateCTM(c, rect.origin.x, rect.size.height - textRect.origin.y - textRect.size.height);
        
        // Finally, draw the text or highlighted text itself (on top of the shadow, if there is one)
        [self drawFramesetter:self.framesetter attributedString:self.attributedString textRange:textRange inRect:textRect context:c];
    } CGContextRestoreGState(c);
}

@end
